Skip to content

fix: function type generation for zero arg and scalar computed fields - #1035

Closed
7ttp wants to merge 3 commits into
supabase:masterfrom
7ttp:fix/fn2
Closed

fix: function type generation for zero arg and scalar computed fields#1035
7ttp wants to merge 3 commits into
supabase:masterfrom
7ttp:fix/fn2

Conversation

@7ttp

@7ttp 7ttp commented Jan 31, 2026

Copy link
Copy Markdown
Member

Problems

  1. Zero arg functions cause columns with same name to be omitted from query types (never extends T is always true)
  2. Scalar computed fields like name_translated(products) are not added to Row types

Solution

  1. Use Record<PropertyKey, never> instead of never for zero-arg function Args
  2. Include functions with single unnamed table/view parameters in filtering logic

Related

Comment thread src/server/templates/typescript.ts
Comment on lines +201 to +203
!getTableNameFromRelationId(func.return_type_relation_id, func.return_type_id)) ||
// OR if the function takes a table/view row (computed field)
tableAndViewNames.has(func.argument_types)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return fns
.map(({ fn, inArgs }) => {
let argsType = 'never'
let argsType = 'Record<PropertyKey, never>'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be tested with the inference within https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js as I expect this might break a few things.

I remember having to use never specifically here to be able to detect the difference between actual functions with args, and functions with no args. Using a Record<string, never> in my memory didn't allowed that.

@prem-2006

prem-2006 commented Feb 18, 2026

Copy link
Copy Markdown

I am working on it, can you assign this issue to me.

Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
@prem-2006 prem-2006 mentioned this pull request Feb 18, 2026
prem-2006 added a commit to prem-2006/postgres-meta that referenced this pull request Feb 19, 2026
prem-2006 added a commit to prem-2006/postgres-meta that referenced this pull request Feb 19, 2026
prem-2006 added a commit to prem-2006/postgres-meta that referenced this pull request Feb 19, 2026
@zlotnika

zlotnika commented Jul 4, 2026

Copy link
Copy Markdown

We'd really love to see this land — we just hit a second, independent breakage from Args: never beyond #1039.

We run a JSON-schema validation test over our generated types: ts-json-schema-generator converts the Database type to JSON Schema so we can validate JSONB rows against our column type overrides. An object with a required never property is uninhabited, so the generator (correctly, type-theoretically) collapses it to never — and that cascades: two zero-arg functions → Functions uninhabited → public uninhabited → the entire Database type becomes never/opaque.

Our workaround until this merges is injecting a custom node parser that rewrites never property signatures to unknown:

createParser(program, config, (p) =>
  p.addNodeParser({
    supportsNode: (n) => n.kind === ts.SyntaxKind.NeverKeyword && ts.isPropertySignature(n.parent),
    createType: () => new UnknownType(),
  }),
)

never really does mean "this function can never be called" — any tool doing sound type math on the generated types will fall over. Record<PropertyKey, never> (as in CLI v1.x and this PR) is the accurate type for "callable with no args".

@spydon

spydon commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Thank you for the contribution! postgres-meta's type generation is moving to the shared @supabase/postgrest-typegen package in supabase/sdk (see #1084), so open template fixes are being re-landed there. The zero-argument Args part of this fix has been ported in supabase/sdk#125 (as Record<PropertyKey, never>) with credit to this PR. The computed-field filtering half is already covered there, since the package's introspection includes table row types.

@spydon spydon closed this Aug 31, 2026
spydon added a commit to supabase/sdk that referenced this pull request Aug 31, 2026
… Args, trigger-writable views (#125)

## Summary

Ports the worthwhile TypeScript generator fixes from postgres-meta's
open template PRs into this package (the templates are being deleted in
favor of this package in supabase/postgres-meta#1084, so open fixes
there are triaged and re-landed here). Four fixes, one commit each:

1. **Stored generated columns omitted from Insert/Update** (from
supabase/postgres-meta#1105): `GENERATED ALWAYS AS ... STORED` columns
reject writes in Postgres, but only identity-ALWAYS columns were
excluded; both now emit `?: never`.
2. **Non-nullable json narrowed to `NonNullable<Json>`** (from
supabase/postgres-meta#1085): the emitted `Json` type includes `null`,
so a NOT NULL json/jsonb column structurally permitted null. Known
accepted edge: a NOT NULL jsonb column holding a JSON `'null'::jsonb`
value still serializes as JS `null`, so Row is optimistic in that case;
Insert/Update narrowing is fully sound.
3. **Zero-argument function Args typed `Record<PropertyKey, never>`**
(the still-valid half of supabase/postgres-meta#1035): `Args: never`
makes postgrest-js treat every zero-argument function as a computed
field (`never extends { '': Row }` always holds), dropping same-named
columns from `select('*')` results, and an uninhabited `Database` breaks
sound type tooling. Verified against postgrest-js, whose
`IsMatchingArgs` special-cases `Record<PropertyKey, never>`.
4. **Insert/Update types for INSTEAD OF trigger views** (from
supabase/postgres-meta#1062, reimplemented): views made writable by
INSTEAD OF triggers got no Insert/Update types. Views now carry
`is_insert_enabled`/`is_update_enabled` computed via
`pg_relation_is_updatable(oid, true)` (bit 8 INSERT, bit 4 UPDATE; also
covers INSTEAD rules), gated independently, and column updatability
counts triggers too (`pg_column_is_updatable(oid, attnum, true)` plus an
explicit INSTEAD OF INSERT trigger check, since that function only
considers the UPDATE event). The origin PR duplicated hand-rolled
pg_trigger subqueries with one pair of wrong bit values and left
trigger-writable columns degrading to `?: never`, visible in its own
snapshot. The two new `PostgresView` fields are additive (metadata
version stays 1), documented, and mirrored in the frozen equivalence
contract.

## Triage of origin PRs

| postgres-meta PR | Verdict | Reasoning |
|---|---|---|
| #1105 | Ported | Two-line correctness fix; `is_generated` was already
introspected. |
| #1085 | Ported | Nullability chokepoint fix; function returns and
composite attributes untouched. |
| #1035 | Ported (zero-arg half) | The computed-field-filtering half is
superseded: this package introspects with `includeTableTypes: true`, so
table/view row types already resolve (parity golden shows computed
fields working). Only foreign-table row types remain uncovered; the PR's
name-string matching is too fragile to port for that niche. |
| #1062 | Reimplemented | Right idea, broken execution (wrong tgtype
bits in one duplicated subquery pair, all-`never` Update output in its
own snapshot). |
| #1063 (TS part) | Skipped | Superseded: composite attributes already
emit `| null` on main; the PR's remaining delta (`unknown | null`) is
the identical type. |
| #1048 (vector to `number[]`) | Skipped | Wrong as a global remap:
PostgREST serializes pgvector as strings in responses, so Row types
would regress; the reviewer asked for e2e evidence and got none. Needs
input/output-aware mapping, a design discussion. |
| #973 (`| string` numeric inserts) | Skipped | Maintainer requested
changes: breaking for consumers expecting `number`; per-column overrides
are the escape hatch. |
| #573 | Skipped | Blanket `| null` on function args/returns is breaking
(author concedes); the centralization half is superseded by the current
generator; the domain-resolution gap is real but needs a metadata
contract extension (feature-scale, raised separately). |
| #750 (`Json` to `unknown`) | Skipped | Breaking; major-version
decision. |
| #1044 (int8 to `bigint`) | Skipped | Breaking, and incorrect without a
custom JSON parser. |
| #1083 (`bigint_as` option) | Skipped | Feature/option with API design
questions, not a fix. |
| #814 (json_schema constraint types) | Skipped | New feature. |

## Validation

- Unit tests per fix, plus Docker-backed introspection integration tests
proving a join view with an INSTEAD OF INSERT trigger introspects as
insert-enabled/update-disabled with updatable columns, and
auto-updatable views keep both flags.
- Parity golden regenerated and reviewed line by line: the only change
is 14 zero-argument functions switching `Args: never` to `Args:
Record<PropertyKey, never>`. Fixes 1, 2 and 4 have no fixture-visible
effect.
- `check-types`, `format-and-lint`, `knip`, `build`, `test` (99 pass
across 12 files) all green.
- Note: the nightly parity job against real postgres-meta will show this
intentional drift until postgres-meta consumes a release containing it
(supabase/postgres-meta#1084 replaces the templates with this package,
closing the gap).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

5 participants